package genetischeralgorithmus;

import java.awt.*;
import java.util.*;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Dominik
 */
public class Algorithmus {

    protected Fitness fit1;
    protected Mutation mut1;
    protected Population pop1;
    protected Recombination recomb1;
    protected Selection selec1;
    protected GenetischerAlgorithmusView view;
    protected Print print1;
    protected Parameter param1;
    int placesCount;
    double[] bestList;
    double[] averageList;
    int countUnchangedBest;
    double lastBest;

    public Algorithmus(GenetischerAlgorithmusView view) {

        fit1 = new Fitness();
        mut1 = new Mutation();
        pop1 = new Population();
        recomb1 = new Recombination();
        print1 = new Print(view);

        placesCount = 0;
        this.view = view;
    }

    public void startTravelingSalesman() {
        param1 = new Parameter(view);
        selec1 = new Selection(param1);

        countUnchangedBest = 0;
        lastBest = 0;

        fit1.setupMatrix();
        //fit1.printDistanceMatrix();
        pop1.initialise(1, placesCount, param1.getStartPopulation());

        bestList = new double[param1.getMaxGeneration()];
        averageList = new double[param1.getMaxGeneration()];

        for (int i = 0; i < param1.getMaxGeneration(); i++) {
            //Erschafft neue Generation aus der alten//
            pop1 = nextGeneration(pop1, i);
            //////////////////!!///////////////////////
        }


        //punkte neu einzeichnen
        view.canvas1.getGraphics().clearRect(0, 0, 500, 500);
        Graphics g = view.canvas1.getGraphics();
        g.setColor(Color.red);
        for (int p = 0; p < fit1.getPlaces().size(); p++) {
            g.drawOval((int) fit1.getPlaces().get(p).getX(), (int) fit1.getPlaces().get(p).getY(), 5, 5);
        }
        view.currGen.setText("" + param1.getMaxGeneration());
        view.currPopSize.setText("" + pop1.getEntities().length);
        view.currBest.setText("" + Math.round(selec1.getRankingArray(fit1.rate(pop1, 1))[0][0]));
        print1.printPath(pop1.entities[(int) (selec1.getRankingArray(fit1.rate(pop1, 1))[0][1])].getPath(), fit1.getPlaces());

        //Darstellung des Konvergenzverhaltens im CanvasField        
        double worstSolution = averageList[0] + averageList[0] * 0.1; //berechnet schlechtestes Ergebnis mit Puffer für skalierung
        double relation = 100 / worstSolution; //skala relation berechnen


        Graphics g1 = view.convergenceCanvas.getGraphics();
        g1.clearRect(0, 0, 502, 500);
        for (int k = 0; k < averageList.length; k++) {
            g1.setColor(Color.black);
            g1.drawOval(k + 1, (int) (averageList[k] * relation), 1, 1);
            g1.setColor(Color.red);
            g1.drawOval(k + 1, (int) (bestList[k] * relation), 1, 1);
        }

    }

    public Population nextGeneration(Population pop, int generation) {


        LinkedList<Double> fitnessList = fit1.rate(pop1, 1);   //Liste mit Fitnesswerten (index=index bei entities)
        double[][] rankingArray = selec1.getRankingArray(fitnessList); // [Fitnesswert][index bei entities] aufsteigend sotiert (=bester am anfang)
        view.currBest.setText(rankingArray[0][1] + ": " + rankingArray[0][0]);

        //für statistik:
        bestList[generation] = rankingArray[0][0];
        int sum = 0;
        for (int h = 0; h < rankingArray.length; h++) {
            sum += rankingArray[h][0];
        }
        averageList[generation] = sum / rankingArray.length;



        int elite = (int) (pop.getEntities().length * (param1.getElite() / 100.0));  //Anzahl der Entities,. die mit in die nächste Generation uebernommen werden
        int newPopulationSize = (int) (pop.getEntities().length * (1 + param1.getPopulationGrowth() / 100)); // neue groeße der Population
        if (newPopulationSize > param1.getMaxPopulationSize()) {
            newPopulationSize = param1.getMaxPopulationSize();  //Wachstumsbeschraenkung der Population beruecksichtigen
        }


        Population newPopulation = new Population(); // neue Generation
        Entity[] newEntities = new Entity[newPopulationSize];  //neues Array für Entity


        int i = 0;
        int l = 0;
        int help = elite;

        while (help > 0) {  //uebernimmt die besten Loesungen
            if (pop.getEntities()[(int) (rankingArray[i][1])].getAge() < param1.getMaxEntityAge()) {  //zu alte Loesungen werden nicht uebernommen
                newEntities[l] = pop.getEntities()[(int) (rankingArray[i][1])];  //eintragen in neue Generation
                newEntities[l].setAge(newEntities[l].getAge() + 1); //eine Generation altern lassen.
                l++;
                help--;
            }
            i++;
        }


        Entity[][] RecombinationPairs = selec1.getRecomninationPairs(rankingArray, pop, (newPopulationSize - elite), rankingArray[rankingArray.length - 1][0], param1.getSelection());

        Entity[] offspring = recomb1.crossover(RecombinationPairs, param1.getRecombination(), param1.getCoding(), fit1.getPlaces().size());

        Entity[] mutatedOffspring = mut1.mutate(offspring, param1.getMutation(), param1.getMutationDegree());



        int k = 0;
        for (int j = elite; j < newEntities.length; j++) {
            newEntities[j] = mutatedOffspring[k++];
        }

        newPopulation.setEntities(newEntities);


        //epidemie operator////////
        if (bestList[generation] == lastBest) {
            countUnchangedBest++; //zählt generationen in der sich beste Lösung nicht verändert
        }
        else{
            countUnchangedBest=0;

        }
        lastBest = bestList[generation];

        if (param1.isEpidemic() && countUnchangedBest > param1.getEpidemiccount()) {
            countUnchangedBest = 0;
            lastBest = 0;
            return selec1.spreadPlague(fit1, newPopulation);
        }
        ///ende epidemie operator////


        return newPopulation;
    }

    public void addPoint(Point p1) {
        fit1.addplace(p1); // übergibt den eingetragenen Punkt an die FitnessKlasse
        placesCount++;  //Anzahl der Orte zählt einen hoch
    }

    public void clear() {
        view.canvas1.getGraphics().clearRect(0, 0, 500, 500);
        view.convergenceCanvas.getGraphics().clearRect(0, 0, 500, 500);
        view.destinationlist.setText("");
        pop1.clear();
        fit1.clear();
        placesCount = 0;
    }
}
